mod req;

use crate::core::entity::whitelist_key::WhitelistKey;
use crate::core::model::resource_version_entry::ResourceVersionEntry;
use crate::custom_filter::{CustomFilter, WhitelistSettings};
use crate::error::{IntoWithStatus, ToStatusCode};
use crate::nested_map::NestedMap;
use crate::router::whitelist_router::Error::{InvalidDeviceId, NotFound};
use crate::router::whitelist_router::req::{PostManyWhitelistRequest, PostOrPutWhitelistRequest};
use share::core::channel::Channel;
use share::core::device_id;
use share::core::device_id::DeviceId;
use share::core::native_version::NativeVersion;
use snafu::{OptionExt, ResultExt, Snafu};
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::instrument::WithSubscriber;
use warp::http::StatusCode;
use warp::{Filter, Rejection, Reply};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display(
        "Can not find whitelist, channel: {channel}, native version: {native_version}"
    ))]
    NotFound {
        channel: Channel,
        native_version: NativeVersion,
    },

    #[snafu(display("Can not convert to device id -> {source}"))]
    InvalidDeviceId { source: device_id::Error },
}

impl ToStatusCode for Error {
    fn status_code(&self) -> StatusCode {
        match self {
            NotFound { .. } => StatusCode::NOT_FOUND,
            InvalidDeviceId { .. } => StatusCode::BAD_REQUEST,
        }
    }
}

type Result<T> = std::result::Result<T, Error>;

pub struct Implementation {}

impl Implementation {
    async fn post(
        req: PostOrPutWhitelistRequest,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let channel = req.channel;
        let native_version = req.native_version;

        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await
            .map_err(|_| NotFound {
                channel,
                native_version,
            })?;

        whitelist.insert(req.key, req.resource_version);

        Ok(())
    }

    async fn post_many(
        req: PostManyWhitelistRequest,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let channel = req.channel;
        let native_version = req.native_version;

        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await
            .map_err(|_| NotFound {
                channel,
                native_version,
            })?;

        for (key, resource_version) in req.entries.into_iter() {
            whitelist.insert(key, resource_version);
        }

        Ok(())
    }

    async fn put(
        req: PostOrPutWhitelistRequest,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let channel = req.channel;
        let native_version = req.native_version;

        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await
            .map_err(|_| NotFound {
                channel,
                native_version,
            })?;

        whitelist.insert(req.key, req.resource_version);

        Ok(())
    }

    async fn delete_by_ip(
        channel: Channel,
        native_version: NativeVersion,
        ip: IpAddr,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await
            .map_err(|_| NotFound {
                channel,
                native_version,
            })?;

        whitelist.remove(&WhitelistKey::Ip(ip));

        Ok(())
    }

    async fn delete_by_device_id(
        channel: Channel,
        native_version: NativeVersion,
        device_id: String,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let device_id = DeviceId::new(&device_id).context(InvalidDeviceIdSnafu {})?;

        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await
            .map_err(|_| NotFound {
                channel,
                native_version,
            })?;

        whitelist.remove(&WhitelistKey::DeviceId(device_id));

        Ok(())
    }

    async fn clear(
        channel: Channel,
        native_version: NativeVersion,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<()> {
        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await
            .map_err(|_| NotFound {
                channel,
                native_version,
            })?;

        whitelist.clear();

        Ok(())
    }

    async fn get(
        channel: Channel,
        native_version: NativeVersion,
        whitelist_settings: Arc<RwLock<WhitelistSettings>>,
    ) -> Result<HashMap<WhitelistKey, ResourceVersionEntry>> {
        let whitelist = whitelist_settings
            .get_by_channel_and_native_version(&channel, &native_version)
            .await
            .map_err(|_| NotFound {
                channel,
                native_version,
            })?;

        let result = whitelist.get_all();

        Ok(result)
    }
}

struct Router {}

impl Router {
    fn post(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.whitelist_settings())
            .then(|req, whitelist_settings| async {
                Implementation::post(req, whitelist_settings)
                    .await
                    .into_with_status()
            })
    }

    fn post_many(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::post()
            .and(warp::path::path("many"))
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.whitelist_settings())
            .then(|req, whitelist_settings| async {
                Implementation::post_many(req, whitelist_settings)
                    .await
                    .into_with_status()
            })
    }

    fn put(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::put()
            .and(warp::path::end())
            .and(warp::body::json())
            .and(filter.whitelist_settings())
            .then(|req, whitelist_settings| async {
                Implementation::put(req, whitelist_settings)
                    .await
                    .into_with_status()
            })
    }

    fn delete_by_ip(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::path("by"))
            .and(warp::path::path("ip"))
            .and(warp::path::param())
            .and(warp::path::param())
            .and(warp::path::param())
            .and(warp::path::end())
            .and(filter.whitelist_settings())
            .then(|channel: Channel, native_version: NativeVersion, ip, whitelist_settings| async move {
                Implementation::delete_by_ip(channel, native_version, ip, whitelist_settings)
                    .await
                    .into_with_status()
            })
    }

    fn delete_by_device_id(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::path("by"))
            .and(warp::path::path("device_id"))
            .and(warp::path::param())
            .and(warp::path::param())
            .and(warp::path::param())
            .and(warp::path::end())
            .and(filter.whitelist_settings())
            .then(
                |channel, native_version, device_id, whitelist_settings| async move {
                    Implementation::delete_by_device_id(
                        channel,
                        native_version,
                        device_id,
                        whitelist_settings,
                    )
                    .await
                    .into_with_status()
                },
            )
    }

    fn clear(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::delete()
            .and(warp::path::path("clear"))
            .and(warp::path::param())
            .and(warp::path::param())
            .and(warp::path::end())
            .and(filter.whitelist_settings())
            .then(
                |channel: Channel, native_version: NativeVersion, whitelist_settings| async move {
                    Implementation::clear(channel, native_version, whitelist_settings)
                        .await
                        .into_with_status()
                },
            )
    }

    fn get(
        filter: Arc<CustomFilter>,
    ) -> impl warp::Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
        warp::get()
            .and(warp::path::param())
            .and(warp::path::param())
            .and(warp::path::end())
            .and(filter.whitelist_settings())
            .then(
                |channel: Channel, native_version: NativeVersion, whitelist_settings| async move {
                    Implementation::get(channel, native_version, whitelist_settings)
                        .await
                        .into_with_status()
                },
            )
    }
}

pub fn get_all_router(
    filter: Arc<CustomFilter>,
) -> impl Filter<Extract = (impl Reply,), Error = Rejection> + Clone {
    warp::path::path("whitelist").and(
        Router::post(filter.clone())
            .or(Router::post_many(filter.clone()))
            .or(Router::put(filter.clone()))
            .or(Router::delete_by_ip(filter.clone()))
            .or(Router::delete_by_device_id(filter.clone()))
            .or(Router::clear(filter.clone()))
            .or(Router::get(filter.clone())),
    )
}
